package cn.dengta.context.rpc;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.annotation.Resource;

import cn.dengta.common.util.SpringUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.Getter;
import me.codeplayer.util.JSONUtil;
import me.codeplayer.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 缓存配置管理器
 */
@Getter
@Component
public class CachedConfigManager implements InitializingBean {

	final String prefix;
	final String group;

	static final transient ConcurrentMap<String, Listener> listeners = new ConcurrentHashMap<>();
	static volatile CachedConfigManager instance;
	static Set<CachedConfig<?>> delaySet = new HashSet<>();
	static final Logger LOGGER = LoggerFactory.getLogger(CachedConfigManager.class);

	public CachedConfigManager(@Value("${nacos.config.cache.prefix:APP-config-}") String prefix,
	                           @Value("${spring.cloud.nacos.config.group:DEFAULT_GROUP}") String group) {
		this.prefix = prefix;
		this.group = group;
	}

	@Resource
	protected NacosConfigManager nacosConfigManager;

	public String dataId(String key) {
		return prefix + key + ".json";
	}

	/**
	 * 创建一个缓存配置
	 *
	 * @param key 缓存配置 key（内部自动添加前后缀）
	 * @param configClass 配置类名
	 * @param loader 如果加载不到配置，则通过该加载器获取配置
	 */
	public static <T> CachedConfig<T> create(String key, Class<T> configClass, Supplier<T> loader) {
		return create(key, loader, JSONUtil::encode, json -> JSON.parseObject(json, configClass));
	}

	/**
	 * 创建一个缓存配置（默认使用 类名 作为 缓存key）
	 *
	 * @param configClass 配置类名
	 * @param loader 如果加载不到配置，则通过该加载器获取配置
	 */
	public static <T> CachedConfig<T> create(Class<T> configClass, Supplier<T> loader) {
		return create(configClass.getSimpleName(), loader, JSONUtil::encode, json -> JSON.parseObject(json, configClass, Feature.SupportAutoType));
	}

	/**
	 * 初始化加载配置并（可选）监听配置变更
	 */
	@Nullable
	public String initAndWatch(String dataId, long timeoutMs, @Nullable Listener listener) {
		final String config;
		try {
			if (listener == null) {
				config = nacosConfigManager.getConfigService().getConfig(dataId, group, timeoutMs);
			} else {
				config = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, timeoutMs, listener);
			}
		} catch (NacosException e) {
			SpringUtil.logError(LOGGER, "加载 Nacos 配置时出错：" + dataId, e);
			return null;
		}
		if (listener == null) {
			LOGGER.info("加载 Nacos 配置：{} => {}", dataId, config);
		} else {
			LOGGER.info("加载 Nacos 配置并监听：{} => {}", dataId, config);
		}
		return config;
	}

	/**
	 * 尝试初始化配置。如果初始化失败，则加入延迟初始化的集合
	 */
	public static boolean tryInitConfig(CachedConfig<?> config) {
		CachedConfigManager manager = instance;
		if (manager == null) {
			synchronized (CachedConfigManager.class) {
				if ((manager = instance) == null) {
					delaySet.add(config);
					LOGGER.info("初始化加载 Nacos 配置失败，转为懒加载：{}", config.key);
					return false;
				}
			}
		}
		doInit(manager, config);
		return true;
	}

	/**
	 * 真正执行配置初始化
	 */
	static void doInit(CachedConfigManager manager, CachedConfig<?> config) {
		final Listener old = listeners.put(config.key, config.listener);
		config.init(manager);
		if (old != null && old != config.listener) { // 如果之前已经有监听器，则移除之
			manager.unwatch(manager.dataId(config.key), old);
		}
	}

	/**
	 * 移除监听器
	 */
	void unwatch(String dataId, Listener listener) {
		try {
			LOGGER.info("移除 Nacos 配置监听器：{}", dataId);
			nacosConfigManager.getConfigService().removeListener(dataId, group, listener);
		} catch (Exception e) {
			SpringUtil.logError(LOGGER, "移除 Nacos 配置监听器出错：" + dataId, e);
		}
	}

	/**
	 * 发布/更新配置
	 *
	 * @param dataId 配置ID
	 * @param content 配置内容
	 * @param prevVersionMd5 上一版本配置的 MD5值（如果本次与上一次配置内容相同，则不覆盖）
	 */
	public boolean publishConfig(String dataId, String content, @Nullable String prevVersionMd5) {
		try {
			LOGGER.info("发布 Nacos 配置：{} => {}", dataId, content);
			final String type = ConfigType.JSON.getType();
			return StringUtil.isEmpty(prevVersionMd5) ? nacosConfigManager.getConfigService().publishConfig(dataId, group, content, type)
					: nacosConfigManager.getConfigService().publishConfigCas(dataId, group, content, prevVersionMd5, type);
		} catch (NacosException e) {
			throw new IllegalStateException("同步 Nacos 配置失败，请稍后重试！", e);
		}
	}

	/**
	 * 发布/更新配置
	 *
	 * @param dataId 配置ID
	 * @param content 配置内容
	 */
	public boolean publishConfig(String dataId, String content) {
		return publishConfig(dataId, content, null);
	}

	/**
	 * 创建一个缓存配置
	 *
	 * @param key 缓存配置 key（内部自动添加前后缀）
	 * @param loader 如果加载不到配置，则通过该加载器获取配置
	 * @param serializer 序列化器
	 * @param deserializer 反序列化器
	 */
	public static <T> CachedConfig<T> create(String key, Supplier<T> loader, Function<? super T, String> serializer, Function<String, T> deserializer) {
		Objects.requireNonNull(key);
		Objects.requireNonNull(loader);
		Objects.requireNonNull(serializer);
		Objects.requireNonNull(deserializer);
		return new CachedConfig<>(key, loader, serializer, deserializer);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		CachedConfigManager manager;
		instance = manager = this;
		synchronized (CachedConfigManager.class) {
			if (!delaySet.isEmpty()) {
				final Iterator<CachedConfig<?>> it = delaySet.iterator();
				while (it.hasNext()) {
					final CachedConfig<?> next = it.next();
					doInit(manager, next);
					it.remove();
				}
			}
		}
	}

}
